%%%-------------------------------------------------------------------
%%%    BASIC INFORMATION
%%%-------------------------------------------------------------------
%%% @copyright 2006 Erlang Training & Consulting Ltd
%%% @author  Martin Carlson <martin@erlang-consulting.com>
%%% @version 0.0.1
%%% @doc
%%% Interface to the sql application and the the attached sql drivers.
%%%
%%% Sideffects: sql_pool_mgr_srv, SQL
%%% All operations on this API will cause changes in the sql_pool_mgr_srv
%%% as well as in SQL
%%% @end
%%%-------------------------------------------------------------------
-module(sql).
-author("support@erlang-consulting.com").
-copyright("Erlang Training & Consulting Ltd").
-vsn("$Rev$").

%% API
-export([transaction/0,
	 transaction/1,
	 commit/0,
	 commit/1,
	 rollback/0,
	 rollback/1,
	 q/1,
	 q/2,
	 q/3,
	 to_sql_date/1,
	 to_sql_time/1,
	 escape/1]).

%% Driver Interface
-export([register_resource/2,
	 register_resource/3, 
	 unregister_resource/1,
	 unregister_resource/2]).

%% Global types
%% @type query() = string(). 
%% SQL conformant query with omitted semicolon
%%
%% @type result() = [] | [Row::tuple()] | {sql_error, term()}. 
%% Query results, rows are represented as tuples and 
%% values are converted to the closest erlang type 
%%
%% @type pool() = atom(). 
%% Identifier of a configured pool in sys.config 

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% @spec transaction() -> ok 
%% @doc Start a sql transaction on the default pool
%% @end
%%--------------------------------------------------------------------
transaction() ->
    {ok, Ref, Mod} = sql_pool_mgr_srv:allocate(),
    do_query("BEGIN", Ref, Mod),
    ok.

%%--------------------------------------------------------------------
%% @spec transaction(Pool::pool()) -> ok 
%% @doc Start a sql transaction on `Pool'
%% @end
%%--------------------------------------------------------------------
transaction(Pool) ->
    {ok, Ref, Mod} = sql_pool_mgr_srv:allocate(Pool),
    do_query("BEGIN", Ref, Mod),
    ok.

%%--------------------------------------------------------------------
%% @spec commit() -> ok 
%% @doc Commit a sql transaction on the default pool
%% @end
%%--------------------------------------------------------------------
commit() ->
    commit(default).

%%--------------------------------------------------------------------
%% @spec commit(Pool::pool()) -> ok 
%% @doc Commit a sql transaction on `Pool'
%% @end
%%--------------------------------------------------------------------
commit(Pool) ->
    case sql_pool_mgr_srv:reference(Pool) of
	{ok, Ref, Mod} ->
	    do_query("COMMIT", Ref, Mod),
	    sql_pool_mgr_srv:deallocate(Pool);
	_ ->
	    exit(no_transaction)
    end.

%%--------------------------------------------------------------------
%% @spec rollback() -> ok 
%% @doc Rollback a sql transaction on the default pool
%% @end
%%--------------------------------------------------------------------
rollback() ->
    rollback(default).

%%--------------------------------------------------------------------
%% @spec rollback(Pool::pool()) -> ok 
%% @doc Rollback a sql transaction on `Pool'
%% @end
%%--------------------------------------------------------------------
rollback(Pool) ->
    case sql_pool_mgr_srv:reference(Pool) of
	{ok, Ref, Mod} ->
	    do_query("ROLLBACK", Ref, Mod),
	    sql_pool_mgr_srv:deallocate(Pool);
	_ ->
	    exit(no_transaction)
    end.

%%--------------------------------------------------------------------
%% @spec q(Query::query()) -> result() 
%% @doc Place a sql query on the default pool
%% @end
%%--------------------------------------------------------------------
q(Query) ->
    q(Query, default).

%%--------------------------------------------------------------------
%% @spec q(Template::query(), Args::args(), Pool::pool()) -> result() 
%% @doc Place a formatted sql query on `Pool'
%% OBS: This is not the same as a parameter query or a prepared statement
%% Template based query where `Template' is a string with placeholders
%% for arguments in `args' 
%% @see //io. io 
%% @end
%%--------------------------------------------------------------------
q(Template, Args, Pool) ->
    Query = io_lib:format(Template, Args),
    q(Query, Pool).

%%--------------------------------------------------------------------
%% @spec q(Template::query(), Args::args()) -> result() 
%% @doc Place a formatted sql query on the default pool
%% OBS: This is not the same as a parameter query or a prepared statement
%% Template based query where `Template' is a string with placeholders
%% for arguments in `args' 
%% @see //io. io
%% @end
%%--------------------------------------------------------------------
q(Template, Args) when is_list(Args) ->
    q(Template, Args, default);

%%--------------------------------------------------------------------
%% @spec q(Query::query(), Pool::pool()) -> result() 
%% @doc Place a sql query on `Pool'
%% @end
%%--------------------------------------------------------------------
q(Query, Pool) when is_atom(Pool) ->
    case sql_pool_mgr_srv:reference(Pool) of
	unbound ->
	    {ok, Res, Mod} = sql_pool_mgr_srv:allocate(Pool),
	    Result = do_query(Query, Res, Mod),
	    sql_pool_mgr_srv:deallocate(Pool),
	    Result;
	{ok, Res, Mod} ->
	    do_query(Query, Res, Mod)
    end.

%%--------------------------------------------------------------------
%% @spec to_sql_date(Date::tuple()) -> string() 
%% @doc Convert erlang date tuple to a sql date string
%% @end
%%--------------------------------------------------------------------
to_sql_date(Date) ->
    sql_lib:to_sql_date(Date).

%%--------------------------------------------------------------------
%% @spec to_sql_time(Time::tuple()) -> string() 
%% @doc Convert erlang time tuple to a sql time string
%% @end
%%--------------------------------------------------------------------
to_sql_time(Time) ->
    sql_lib:to_sql_time(Time).

%%--------------------------------------------------------------------
%% @spec escape(String::string()) -> string() 
%% @doc Escape omit -- and escape ' to \' characters
%% @end
%%--------------------------------------------------------------------
escape(String) ->
    sql_lib:escape(String).

%%====================================================================
%% Driver Interface functions
%%====================================================================
%%--------------------------------------------------------------------
%% @spec register_resource(Pid::pid(), Mod::atom()) -> ok | {error, term()} 
%% @doc Register a driver pid and callback with the sql pool manager
%% (This is a sql driver interface and not to be used by other programs)
%% @end
%%--------------------------------------------------------------------
register_resource(Pid, Mod) ->
    sql_pool_mgr_srv:add_resource(Pid, Mod).

%%--------------------------------------------------------------------
%% @spec register_resource(Pid::pid(), Mod::atom(), Name::pool()) -> 
%% ok | {error, term()} 
%% @doc Register a driver pid and callback with the sql pool manager
%% (This is a sql driver interface and not to be used by other programs)
%% @end
%%--------------------------------------------------------------------
register_resource(Pid, Mod, Name) ->
    sql_pool_mgr_srv:add_resource(Pid, Mod, Name).

%%--------------------------------------------------------------------
%% @spec unregister_resource(Pid::pid()) -> ok | {error, term()} 
%% @doc Unregister a driver pid from the sql pool manager
%% (This is a sql driver interface and not to be used by other programs)
%% @end
%%--------------------------------------------------------------------
unregister_resource(Pid) ->
    sql_pool_mgr_srv:remove_resource(Pid).

%%--------------------------------------------------------------------
%% @spec unregister_resource(Pid::pid(), Name::pool()) -> ok | {error, term()} 
%% @doc Unregister a driver pid from the sql pool manager
%% (This is a sql driver interface and not to be used by other programs)
%% @end
%%--------------------------------------------------------------------
unregister_resource(Pid, Name) ->
    sql_pool_mgr_srv:remove_resource(Pid, Name).

%%====================================================================
%% Internal functions
%%====================================================================
do_query(Q, Connection, Mod) ->
    Mod:q(Connection, Q).


